#nullable disable
namespace Crux.Core.MethodEx;
/// <summary>
/// DynamicGetterFactory
/// </summary>
public static class DynamicGetterFactory
{
    /// <summary>
    ///
    /// </summary>
    private static readonly Type ObjectType = typeof(object);
    /// <summary>
    ///
    /// </summary>
    private static readonly Type IlGetterType = typeof(Func<object, object>);

    /// <summary>
    ///
    /// </summary>
    /// <param name="p"></param>
    /// <returns></returns>
    public static DynamicGetter CreateUsingIl(PropertyInfo p)
      => new DynamicGetter(CreateIlGetter(p));
    /// <summary>
    ///
    /// </summary>
    /// <param name="p"></param>
    /// <returns></returns>
    public static DynamicGetter CreateUsingCompiledLambda(PropertyInfo p)
      => new DynamicGetter(CreateLambdaGetter(p.DeclaringType, p));
    /// <summary>
    ///
    /// </summary>
    /// <param name="type"></param>
    /// <param name="property"></param>
    /// <returns></returns>
    private static Func<object, object> CreateLambdaGetter(
      Type type,
      PropertyInfo property)
    {
        var objExpr = Expression.Parameter(ObjectType, "theItem");
        var castedObjExpr = Expression.Convert(objExpr, type);

        var p = Expression.Property(castedObjExpr, property);
        var castedProp = Expression.Convert(p, ObjectType);

        var lambda = Expression.Lambda<Func<object, object>>(castedProp, objExpr);

        return lambda.Compile();
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    private static Func<object, object> CreateIlGetter(PropertyInfo propertyInfo)
    {
        var propGetMethod = propertyInfo.GetGetMethod(true);
        if (propGetMethod is null)
            return null;

        var getter = CreateDynamicGetMethod(propertyInfo);
        var generator = getter.GetILGenerator();

        var x = generator.DeclareLocal(propertyInfo.DeclaringType);//Arg
        var y = generator.DeclareLocal(propertyInfo.PropertyType); //Prop val
        var z = generator.DeclareLocal(ObjectType); //Prop val as obj

        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
        generator.Emit(OpCodes.Stloc, x);

        generator.Emit(OpCodes.Ldloc, x);
        generator.EmitCall(OpCodes.Callvirt, propGetMethod, null);
        generator.Emit(OpCodes.Stloc, y);

        generator.Emit(OpCodes.Ldloc, y);

        if (!propertyInfo.PropertyType.IsClass)
        {
            generator.Emit(OpCodes.Box, propertyInfo.PropertyType);
            generator.Emit(OpCodes.Stloc, z);
            generator.Emit(OpCodes.Ldloc, z);
        }

        generator.Emit(OpCodes.Ret);

        return (Func<object, object>)getter.CreateDelegate(IlGetterType);
    }
    /// <summary>
    ///
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    private static DynamicMethod CreateDynamicGetMethod(PropertyInfo propertyInfo)
    {
        var args = new[] { ObjectType };
        var name = $"_{propertyInfo.DeclaringType.Name}_Get{propertyInfo.Name}_";
        var returnType = ObjectType;

        return !propertyInfo.DeclaringType.IsInterface
               ? new DynamicMethod(
                 name,
                 returnType,
                 args,
                 propertyInfo.DeclaringType,
                 true)
               : new DynamicMethod(
                 name,
                 returnType,
                 args,
                 propertyInfo.Module,
                 true);
    }
}